/*
 * Copyright (c) 2010, Nikolaus Moll
 * All rights reserved.
 */
package net.miblounge.gradecalculator.core.student;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import net.miblounge.gradecalculator.core.AbstractDataProvider;
import net.miblounge.gradecalculator.core.descriptors.University;
import net.miblounge.gradecalculator.core.descriptors.courses.Course;
import net.miblounge.gradecalculator.exceptions.XMLException;

@XmlRootElement(namespace = "http://gradecalculator.net/")
@XmlType( 
 propOrder = { "name", "universityName", "degree", "courseOfStudies", "semester", "selectedModuleLists", "results" } 
)
// TODO: remove unnecessary results before serialization
public class Student implements Cloneable {
	private String name;
	private String universityName;
	private String degree;	// Master, Bachelor, ...
	private University university;
	private String courseOfStudies;
	private String semester;	// version of regulations
	private List<String> selectedModuleLists = new ArrayList<String>();
	private List<ExaminationResult> results = new ArrayList<ExaminationResult>();
	
	@XmlTransient
	private HashMap<Integer, ExaminationResult> cache = new HashMap<Integer, ExaminationResult>();

	public Student() {
		this("");
	}

	public Student(final String name) {
		this.setName(name);
	}

	public String getName() {
		return name;
	}
	public void setName(final String name) {
		this.name = name;
	}

	public String getSemester() {
		return semester;
	}
	public void setSemester(final String semester) {
		this.semester = semester;
	}

	@XmlElementWrapper(name="selections")
	@XmlElement(name="selection")
	public List<String> getSelectedModuleLists() {
		return selectedModuleLists;
	}
	public void setSelectedModuleLists(final List<String> selectedModuleLists) {
		this.selectedModuleLists = selectedModuleLists;
	}

	@XmlElementWrapper(name="results")
	@XmlElement(name = "result")
	public List<ExaminationResult> getResults() {
		// TODO: remove unnecessary results...
		final List<ExaminationResult> workingcopy = new ArrayList<ExaminationResult>(results);
		for (ExaminationResult r : workingcopy) {
			//	remove unnecessary items
			final boolean keep = (r.getGrades().size() > 0) || (r.getUngradedPassed());
			if (!keep) {
				results.remove(r);
			}
		}
		return results;
	}
	public void setResults(final List<ExaminationResult> results) {
		this.results = results;
	}

	@XmlElement(name = "university")
	public String getUniversityName() {
		return universityName;
	}
	public void setUniversityName(final String university) {
		this.universityName = university;
		this.university = null;
	}

	@XmlTransient
	public University getUniversity() {
		if (university == null) {
			university = AbstractDataProvider.getInstance().getUniversity(getUniversityName());
		}
		return university;
	}

	@XmlElement(name = "courseofstudies")
	public String getCourseOfStudies() {
		return courseOfStudies;
	}
	public void setCourseOfStudies(final String courseOfStudies) {
		this.courseOfStudies = courseOfStudies;
	}
	
	public String getSPODescriptionName() {
		return universityName + "." + 
			degree + "." + 
			courseOfStudies + "." + 
			semester;
	}

	public String getDegree() {
		return degree;
	}
	public void setDegree(final String degree) {
		this.degree = degree;
	}


	public void removeResult(final ExaminationResult result) {
		// remove from cache
		if ((result.getExamNumber() != 0) && (result.equals(cache.get(result.getExamNumber())))) {
			cache.remove(result.getExamNumber());
		}
		
		// remove from results
		results.remove(result);
	}

	public void addSelected(final String identifier)
	{
		selectedModuleLists.add(identifier);
	}
	
	public void removedSelected(final String identifier)
	{
		selectedModuleLists.remove(identifier);
	}
	
	public boolean hasSelected(final String identifier) {
		return selectedModuleLists.contains(identifier);
	}

	public void addResult(final ExaminationResult result)
	{
		if (!results.contains(result)) {
			results.add(result);
		}
		if (result.getExamNumber() != 0) {
			cache.put(result.getExamNumber(), result);
		}
	}

	public ExaminationResult getResult(final Course course)
	{
		return getResult(course.getExamNumber(), course.getIdentifier());
	}
	
	public ExaminationResult getResult(final int examNumber, final String courseIdentifier)
	{
		ExaminationResult result = cache.get(examNumber);
		if (result == null) {
			for (ExaminationResult test : results) {
				if (test.getCourseIdentifier().equals(courseIdentifier)) {
					result = test;
					break;
				}
			}
		}
		
		// do not return a null result
		if (result == null) {
			result = new ExaminationResult();
			result.setCourseIdentifier(courseIdentifier);
			result.setExamNumber(examNumber);
			result.setUngradedPassed(false);
			
			if (examNumber != 0) {
				cache.put(examNumber, result);
			}
			results.add(result);
		}

		return result;
	}

	public static Student createFromXML(final String filename) {
		Reader xmlinput = null;
		Student result = null;
		try {
			final JAXBContext context = JAXBContext.newInstance(Student.class);
			final Unmarshaller unmarshaller = context.createUnmarshaller();
			xmlinput = new FileReader(filename);
			result = (Student) unmarshaller.unmarshal(xmlinput);
		} catch (final JAXBException jaxbException) {
			throw new XMLException("Error reading student xml", jaxbException);
		} catch (final Exception e) {
			throw new XMLException("Error reading student xml", e);
		} finally {
			try {
				if (xmlinput != null) {
					xmlinput.close();
				}
			} catch (final IOException e) {
				throw new XMLException("Error closing student xml", e);
			}
		}
		return result;
	}

	public void saveXML(final String filename) {
		Writer xmloutput = null;
		try {
			final Student student = this.clone();
			
			final JAXBContext context = JAXBContext.newInstance(Student.class);
			final Marshaller marshaller = context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			xmloutput = new FileWriter(filename);
			marshaller.marshal(student, xmloutput);
			xmloutput.flush();
		} catch (final JAXBException jaxbException) {
			throw new XMLException("Error writing student xml", jaxbException);
		} catch (final Exception e) {
			throw new XMLException("Error saving student xml", e);
		} finally {
			try {
				if (xmloutput != null) {
					xmloutput.close();
				}
			} catch (final Exception e) {
				throw new XMLException("Error closing student xml", e);
			}
		}
	}
	
	protected int getHashValue(final Object o)
	{
		if (o == null) {
			return 0;
		} else {
			return o.hashCode();
		}
	}
	
	public String toObjectString()
	{
		return super.toString();
	}

	@Override
	public int hashCode()
	{
		int result = 17;
		result = 31 * result + name.hashCode();
		result = 31 * result + getHashValue(universityName);
		result = 31 * result + getHashValue(degree);
		result = 31 * result + getHashValue(courseOfStudies);
		result = 31 * result + getHashValue(semester);
		result = 31 * result + getHashValue(selectedModuleLists);
		result = 31 * result + results.hashCode();
		
		return result;
	}

	@Override
	public Student clone()
	{
		try {
			final Student result = (Student)super.clone();
			result.name = new String(name);
			result.universityName = universityName;
			result.university = university;
			result.degree = new String(degree);
			result.courseOfStudies = new String(courseOfStudies);
			result.semester = semester;
			
			result.selectedModuleLists = new ArrayList<String>();
			for (String moduleList : selectedModuleLists) {
				result.selectedModuleLists.add(new String(moduleList));
			}

			result.results = new ArrayList<ExaminationResult>();
			for (ExaminationResult examResult : results) {
				// TODO check whether clone is necessary
				result.results.add(examResult);
			}
			
			return result;
		} catch (final CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
	
}
